feat(realtime): real-time GTFS-RT departure delay buffer#12
Open
Multipixelone wants to merge 2 commits into
Open
feat(realtime): real-time GTFS-RT departure delay buffer#12Multipixelone wants to merge 2 commits into
Multipixelone wants to merge 2 commits into
Conversation
… (GTFS-RT)
Google Directions plans against the static schedule. When the subway/LIRR/bus
line a rider is about to board is actually running late, fold a capped, observed
delay into the departure buffer so the alarm fires earlier on a degraded-service
morning. Conservative by design: a live delay can only move the leave time
*earlier* (never later) and never claims a precise new arrival.
Mirrors the weather-buffer seam end to end:
- realtime.py: realtime_delay(route, at_time, config) -> RealtimeDelay, with an
injectable fetcher, a 60s per-system TTL feed memo, and a fail-open contract
(any error/miss -> zero buffer, never a failed plan). Matches the first
boarding leg's stop name to GTFS stop_ids via rapidfuzz over bundled tables,
scoped by system; picks the predicted departure closest to schedule; floors at
zero, caps at max_buffer_minutes, ignores sub-threshold jitter.
- gtfs_rt.py: shared feed fetch/validate helper, reused by mta.py (alerts) and
realtime.py (trip updates).
- data/stops_{subway,lirr,bus}.csv + scripts/build_stops.py: bundled GTFS stop
tables (generated from MTA static GTFS) and their refresh generator.
- config.RealtimeConfig (disabled by default); Plan.realtime_buffer_minutes /
realtime_reason; planner folds the delay into the buffer; format.py surfaces a
"+N min - Q running ~6 min late" advisory.
- `commutecompass realtime` diagnostic command + skill-script parity and docs.
Tests: tests/test_realtime.py (delay/cap/threshold/window/fuzzy/system-scoping/
LIRR/bus/fail-open + feed parsing), plus planner and CLI coverage. mypy strict
clean; full suite 645 passing; coverage 84% (>=80% gate).
The nix flake check sandbox sets SSL_CERT_FILE to a non-existent path, so constructing a real httpx.Client in _fetch_predictions raised FileNotFoundError during create_ssl_context. The tests mock fetch_feed_message (the I/O) but the client was still built. Patch httpx.Client too, matching the existing test_geocode/test_ha_client idiom, so these tests no longer touch real SSL.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds C1 — real-time GTFS-RT departures: when the subway/LIRR/bus line a rider is about to board is actually running late, fold a capped, observed delay into the departure buffer so the alarm fires earlier on a degraded-service morning.
Conservative by design (the "honest, not misleading" call): a live delay can only move the leave time earlier — never later — and it never claims a precise new arrival (that would need static trip data we don't carry). It mirrors the existing weather-buffer seam end to end.
How it works
realtime.py—realtime_delay(route, at_time, config) -> RealtimeDelay, with an injectablefetcher, a 60s per-system TTL feed memo, and a fail-open contract (any error/miss → zero buffer, never a failed plan). It takes the first boarding leg, fuzzy-matches its stop name to GTFSstop_ids (rapidfuzz over bundled tables, scoped by system), fetches only that system's trip-update feeds, picks the predicted departure closest to schedule, floors at zero, caps atmax_buffer_minutes, and ignores sub-threshold jitter.gtfs_rt.py— shared feed fetch/validate helper, reused bymta.py(alerts) andrealtime.py(trip updates).data/stops_{subway,lirr,bus}.csv(generated from MTA static GTFS byscripts/build_stops.py).RealtimeConfig(disabled by default);Plan.realtime_buffer_minutes/realtime_reason; planner folds the delay into the buffer alongside weather;format.pysurfaces a🚇 +N min — Q running ~6 min lateadvisory.commutecompass realtimediagnostic command,realtime.sh, SKILL.md row, allowlist + example-config docs.Design decisions (settled up front)
stops.txtsubset + rapidfuzzTesting
tests/test_realtime.py: delay/cap/threshold/window/fuzzy/system-scoping/LIRR/bus/fail-open + feed parsing.Packaging note
Couldn't run a real
python -m buildin the dev sandbox (no build frontend / offline). The CSVs are confirmed not gitignored (hatchling bundles them by default) and explicitartifacts/sdist entries were added as backup. Worth a quickpython -m build && unzip -l dist/*.whl | grep stopsbefore release.Out of scope (follow-ups)